package world;

import goals.Goal;
import goals.GoalAttack;
import goals.GoalFlee;
import goals.GoalPickUp;
import goals.ListOfGoals;

import java.io.Serializable;
import java.util.ArrayList;
import control.GUI.Output;

import utility.Vector;

public class Person extends PhysicalObject {

	public class KnownPerson implements Serializable {
		private static final long serialVersionUID = -1367997078648206384L;
		public Person p;
		public Vector lastKnownPosition;
		public double friendliness;
	}

	private static String[] names = { "Aori", "Bl'ilding ", "Corih ",
			"Dia'dyn ", "Esor ", "Fisamlye ", "Irach", "Jikif ", "Kaiss-ban ",
			"Luildelm ", "Mymoge ", "Nys'ryna ", "Orenkineste ", "Pamen ",
			"Riav", "Soh ", "Tascvor ", "Ughtesto ", "Vemaca ", "Warurr ",
			"Yldbur " };

	private static final long serialVersionUID = 8628500342021736779L;
	private double speed, sightRange, attackRange, attackDamage, health,
			aggressiveness;

	private ArrayList<KnownPerson> knownPeople;
	private ArrayList<PhysicalObject> inventory;
	private ListOfGoals goals;

	public Person(Vector position) {
		super(position);
		this.speed = 5 + Math.random() * 5;
		this.sightRange = 20 + Math.random() * 20;
		this.attackRange = 1;
		this.attackDamage = 10 + Math.random() * 10;
		this.health = 100 + Math.random() * 50;
		this.aggressiveness = Math.random() * 100;

		while (name == null) {
			int pos = (int) (Math.random() * names.length);
			this.name = names[pos];
			names[pos] = null;
		}
		knownPeople = new ArrayList<KnownPerson>();
		inventory = new ArrayList<PhysicalObject>();

		goals = new ListOfGoals();
	}

	/**
	 * Checks if a person p is known to this person. If it is known, the last
	 * known position is updated. Otherwise, that person is inserted into the
	 * list. If that person is me, null is returned.
	 */
	public KnownPerson insertOrUpdateKnownPerson(Person p) {
		if (!p.equals(this)) {
			for (KnownPerson k : knownPeople) {
				if (p.equals(k.p)) {
					k.lastKnownPosition = p.getPosition();
					return k;
				}
			}

			// not known -> insert
			KnownPerson kp = new KnownPerson();
			kp.p = p;
			kp.lastKnownPosition = p.getPosition();
			kp.friendliness = Math.random() - 0.5;
			knownPeople.add(kp);
			return kp;

		}
		return null;
	}

	/**
	 * Delete p from this persons database of known people.
	 */
	public boolean deleteKnownPerson(Person p) {
		if (!p.equals(this)) {
			for (int c0 = 0; c0 < knownPeople.size(); c0++) {
				if (p.equals(knownPeople.get(c0).p)) {
					knownPeople.remove(c0);
					return true;
				}
			}
		}
		return false;
	}

	public KnownPerson checkForKnownPerson(Person p) {
		if (!p.equals(this)) {
			for (KnownPerson k : knownPeople) {
				if (p.equals(k.p)) {
					return k;
				}
			}
		}
		return null;
	}

	public Person closestEnemy(ArrayList<PhysicalObject> seen) {
		Person p = null;
		double distance = Double.MAX_VALUE;
		for (PhysicalObject po : seen) {
			if (po instanceof Person) {
				KnownPerson kp = checkForKnownPerson((Person) po);
				if (kp != null) {
					if (kp.friendliness < 0 && kp.p.health > 0) {
						double lDist = this.position.distance(kp.p.position);
						if (lDist < distance) {
							distance = lDist;
							p = kp.p;
						}
					}
				}
			}
		}
		return p;
	}

	public Weapon closestWeapon(ArrayList<PhysicalObject> seen) {
		Weapon p = null;
		double distance = Double.MAX_VALUE;
		for (PhysicalObject po : seen) {
			if (po instanceof Weapon) {
				Weapon wo = (Weapon) po;
				if (wo.owner == null) {
					double lDist = this.position.distance(wo.position);
					if (lDist < distance) {
						distance = lDist;
						p = wo;
					}
				}
			}
		}
		return p;
	}

	private void enforceWorldBarriers(World w) {
		if (position.x < 0) {
			position.x = 0;
		}
		if (position.x > w.getSize().x) {
			position.x = w.getSize().x;
		}
		if (position.y < 0) {
			position.y = 0;
		}
		if (position.y > w.getSize().y) {
			position.y = w.getSize().y;
		}
	}

	private void rebuildGoalList(World w) {
		goals.clear();

		ArrayList<PhysicalObject> seen = w.getObjectsInRange(position,
				sightRange);

		Person enemy = closestEnemy(seen);
		if (enemy != null
				&& position.distance(enemy.getPosition()) > 5 * enemy
						.getAttackRange()) {
			if (1 * (this.getHealth() - enemy.getHealth()) + 0.5
					* aggressiveness >= 0) {
				goals.add(new GoalAttack(this, enemy));
			} else {
				goals.add(new GoalFlee(this, enemy));
			}
		} else {
			Weapon weapon = closestWeapon(seen);
			if (weapon != null) {
				goals.add(new GoalPickUp(this, weapon));
			}
		}
	}

	public void act(World world, Output out) {

		if (this.health <= 0) {
			return;
		}

		boolean finishedGoal;
		if (!goals.isEmpty()) {
			Goal g = goals.peek();
			finishedGoal = g.act(out);
			if (finishedGoal) {
				// for now unnecessary, because it rebuilds later
				goals.poll();
			}
		} else {
			Vector movement = new Vector();
			movement.setPolar(Math.random() * 2 * Math.PI, speed);
			this.setPosition(this.getPosition().add(movement));
			finishedGoal = true;
		}
		enforceWorldBarriers(world);
		// update after movement
		ArrayList<PhysicalObject> seen = world.getObjectsInRange(position,
				sightRange);

		for (PhysicalObject o : seen) {
			if (o instanceof Person) {
				Person p = (Person) o;
				if (p.health > 0) {
					insertOrUpdateKnownPerson(p);
				} else {
					this.deleteKnownPerson(p);
				}
			}
		}

		boolean needToReevaluate = goals.tickTimeAndCheckGoalTimeouts();
		if (finishedGoal || needToReevaluate) {
			rebuildGoalList(world);
		}
	}

	public void report(Output out) {
		String s = name + " - " + (int) health + " hp, " + (int) attackDamage
				+ " atk " + getPosition().toString() + "\n";
		for (KnownPerson kp : knownPeople) {
			s += (kp.friendliness >= 0 ? "friend: " : "foe: ") + kp.p.name
					+ "\n";
		}
		s += this.goals + "\n";
		out.push(s + "\n");
	}

	public double getSpeed() {
		return speed;
	}

	public double getSightRange() {
		return sightRange;
	}

	public double getAttackRange() {
		return attackRange;
	}

	public double getAttackDamage() {
		return attackDamage;
	}

	public double getHealth() {
		return health;
	}

	/**
	 * Beware: infinite recursions possible.
	 */
	@Override
	public void setPosition(Vector v) {
		position = v;
		for (PhysicalObject o : inventory) {
			o.setPosition(v);
		}
	}

	public void dealtDamage(double damage) {
		health -= damage;
	}

	public void die() {
		for (PhysicalObject o : inventory) {
			o.owner = null;
		}
		inventory.clear();
	}

	public void addToInventory(PhysicalObject i) {
		if (i instanceof Weapon) {
			Weapon w = (Weapon) i;
			boolean best = true;
			Weapon equippedWeapon = null;
			for (PhysicalObject o : inventory) {
				if (o instanceof Weapon) {
					Weapon wo = (Weapon) o;
					if (w.attackDamageBuff < wo.attackDamageBuff) {
						best = false;
					}
					if (wo.equipped) {
						equippedWeapon = wo;
					}
				}
			}
			if (best) {
				w.equipped = true;
				if (equippedWeapon != null) {
					equippedWeapon.equipped = false;
				}
			}
		}
		inventory.add(i);
	}

	public Goal getActiveGoal(){
		return goals.peek();
	}
}
